---
date: 2023-09-08
layout: learning_zig
title: "Learning Zig - Generics"
tags: [zig]
permalink: "/learning_zig/generics/"
---

<div class=pager>
	<a class=prev href=/learning_zig/heap_memory/>Heap Memory & Allocators</a>
	<a class=home href=/learning_zig/>Intro</a>
	<a class=next href=/learning_zig/coding_in_zig/>Coding In Zig</a>
</div>

<article>
	<section>
		<h1 tabindex=-1 id=generics><a href=#generics aria-hidden=true>Generics</a></h1>
		<p>In the previous part we built a bare-boned dynamic array called <code>IntList</code>. The goal of the data structure was to store a dynamic number of values. Although the algorithm we used would work for any type of data, our implementation was tied to <code>i64</code> values. Enter generics, the goal of which is to abstract algorithms and data structures from specific types.</p>

		<p>Many languages implement generics with special syntax and generic-specific rules. With Zig, generics are less of a specific feature and more of an expression of what the language is capable of. Specifically, generics leverage Zig's powerful compile-time metaprogramming.</p>

		<p>We'll begin by looking at a silly example, just to get our bearings:</p>

{% highlight zig %}
const std = @import("std");

pub fn main() !void {
	var arr: IntArray(3) = undefined;
	arr[0] = 1;
	arr[1] = 10;
	arr[2] = 100;
	std.debug.print("{any}\n", .{arr});
}

fn IntArray(comptime length: usize) type {
	return [length]i64;
}
{% endhighlight %}

		<p>The above prints <code>{ 1, 10, 100 }</code>. The interesting part is that we have a function that returns a <code>type</code> (hence the function is PascalCase). Not just any type either, but a type based on a function parameter. This code only worked because we declared <code>length</code> as <code>comptime</code>. That is, we require anyone who calls <code>IntArray</code> to pass a compile-time known <code>length</code> parameter. This is necessary because our function returns a <code>type</code> and <code>types</code> must always be compile-time known.</p>

		<p>A function can return <em>any</em> type, not just primitives and arrays. For example, with a small change, we can make it return a structure:</p>

{% highlight zig %}
const std = @import("std");

pub fn main() !void {
	var arr: IntArray(3) = undefined;
	arr.items[0] = 1;
	arr.items[1] = 10;
	arr.items[2] = 100;
	std.debug.print("{any}\n", .{arr.items});
}

fn IntArray(comptime length: usize) type {
	return struct {
		items: [length]i64,
	};
}
{% endhighlight %}

		<p>It might seem odd, but <code>arr</code>'s type really is an <code>IntArray(3)</code>. It's a type like any other type and <code>arr</code> is a value like any other value. If we called <code>IntArray(7)</code> that would be a different type. Maybe we can make things neater:</p>

{% highlight zig %}
const std = @import("std");

pub fn main() !void {
	var arr = IntArray(3).init();
	arr.items[0] = 1;
	arr.items[1] = 10;
	arr.items[2] = 100;
	std.debug.print("{any}\n", .{arr.items});
}

fn IntArray(comptime length: usize) type {
	return struct {
		items: [length]i64,

		fn init() IntArray(length) {
			return .{
				.items = undefined,
			};
		}
	};
}
{% endhighlight %}

		<p>At first glance that might not look neater. But besides being nameless and nested in a function, our structure's looking like every other structure we've seen so far. It has fields, it has functions. You know what they say, <em>if it looks like a duck...</em>. Well, this looks, swims and quacks like a normal structure, because it is.</p>

		<p>We've taken this route to get comfortable with a function that returns a type and the accompanying syntax. To get a more typical generic, we need to make one last change: our function has to take a <code>type</code>. In reality, this is a small change, but <code>type</code> can feel more abstract than <code>usize</code>, so we took it slowly. Let's make a leap and modify our previous <code>IntList</code> to work with any type. We'll start with a skeleton:</p>

{% highlight zig %}
fn List(comptime T: type) type {
	return struct {
		pos: usize,
		items: []T,
		allocator: Allocator,

		fn init(allocator: Allocator) !List(T) {
			return .{
				.pos = 0,
				.allocator = allocator,
				.items = try allocator.alloc(T, 4),
			};
		}
	};
}
{% endhighlight %}

		<p>The above <code>struct</code> is almost identical to our <code>IntList</code> except <code>i64</code> has been replaced with <code>T</code>. That <code>T</code> might seem special, but it's just a variable name. We could have called it <code>item_type</code>. However, following Zig's naming convention, variables of type <code>type</code> are PascalCase.</p>

		<aside>For good or bad, using a single letter to represent a type parameter is much older than Zig. <code>T</code> is a common default in most languages, but you will see context-specific variations, such as hash maps using <code>K</code> and <code>V</code> for their key and value parameter types.</aside>

		<p>If you aren't sure about our skeleton, consider the two places we use <code>T</code>: <code>items: []T</code> and <code>allocator.alloc(T, 4)</code>. When we want to use this generic type, we'll create an instance using:</p>

{% highlight zig %}
var list = try List(u32).init(allocator);
{% endhighlight %}

		<p>When the code gets compiled, the compiler creates a new type by finding every <code>T</code> and replacing it with <code>u32</code>. If we use <code>List(u32)</code> again, the compiler will re-use the type it previous created. If we specify a new value for <code>T</code>, say <code>List(bool)</code> or <code>List(User)</code>, new types will be created.</p>

		<p>To complete our generic <code>List</code> we can literally copy and paste the rest of the <code>IntList</code> code and replace <code>i64</code> with <code>T</code>. Here's a full working example:</p>

{% highlight zig %}
const std = @import("std");
const Allocator = std.mem.Allocator;

pub fn main() !void {
	var gpa = std.heap.GeneralPurposeAllocator(.{}){};
	const allocator = gpa.allocator();

	var list = try List(u32).init(allocator);
	defer list.deinit();

	for (0..10) |i| {
		try list.add(@intCast(i));
	}

	std.debug.print("{any}\n", .{list.items[0..list.pos]});
}

fn List(comptime T: type) type {
	return struct {
		pos: usize,
		items: []T,
		allocator: Allocator,

		fn init(allocator: Allocator) !List(T) {
			return .{
				.pos = 0,
				.allocator = allocator,
				.items = try allocator.alloc(T, 4),
			};
		}

		fn deinit(self: List(T)) void {
			self.allocator.free(self.items);
		}

		fn add(self: *List(T), value: T) !void {
			const pos = self.pos;
			const len = self.items.len;

			if (pos == len) {
				// we've run out of space
				// create a new slice that's twice as large
				var larger = try self.allocator.alloc(T, len * 2);

				// copy the items we previously added to our new space
				@memcpy(larger[0..len], self.items);

				self.allocator.free(self.items);

				self.items = larger;
			}

			self.items[pos] = value;
			self.pos = pos + 1;
		}
	};
}
{% endhighlight %}

		<p>Our <code>init</code> function returns a <code>List(T)</code>, and our <code>deinit</code> and <code>add</code> functions take a <code>List(T)</code> and <code>*List(T)</code>. In our simple class, that's fine, but for large data structures, writing the full generic name can become a little tedious, especially if we have multiple type parameters (e.g. a hash map that takes a separate <code>type</code> for its key and value). The <code>@This()</code> builtin function returns the innermost <code>type</code> from where it's called. Most likely, our <code>List(T)</code> would be written as:</p>

{% highlight zig %}
fn List(comptime T: type) type {
	return struct {
		pos: usize,
		items: []T,
		allocator: Allocator,

		// Added
		const Self = @This();

		fn init(allocator: Allocator) !Self {
			// ... same code
		}

		fn deinit(self: Self) void {
			// .. same code
		}

		fn add(self: *Self, value: T) !void {
			// .. same code
		}
	};
}
{% endhighlight %}

		<p><code>Self</code> isn't a special name, it's just a variable, and it's PascalCase because its value is a <code>type</code>. We can use <code>Self</code> where we had previously used <code>List(T)</code>.</p>
	</section>

	<section>
		<p>We could create more complex examples, with multiple type parameters and more advanced algorithms. But, in the end, the core generic code would be no different than the simple examples above. In the next part we'll touch on generics again when we look at the standard library's <code>ArrayList(T)</code> and <code>StringHashMap(V)</code>.</p>
	</section>
</article>

<div class=pager>
	<a class=prev href=/learning_zig/heap_memory/>Heap Memory & Allocators</a>
	<a class=home href=/learning_zig/>Intro</a>
	<a class=next href=/learning_zig/coding_in_zig/>Coding In Zig</a>
</div>
